/**
 * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springblade.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.config.MqQueueConfig;
import org.springblade.common.tool.ThreadPoolUtils;
import org.springblade.gateway.props.AuthProperties;
import org.springblade.gateway.provider.AuthProvider;
import org.springblade.gateway.provider.ResponseProvider;
import org.springblade.gateway.utils.JwtUtil;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 鉴权认证
 * @author Chill
 */
@Slf4j
@Component
@AllArgsConstructor
public class AuthFilter implements GlobalFilter, Ordered {
	private final AuthProperties authProperties;
	private final ObjectMapper objectMapper;
	private final AntPathMatcher antPathMatcher = new AntPathMatcher();
	private static BlockingQueue<String> auditList = new ArrayBlockingQueue<>(1000000);

	@Resource
	private RabbitTemplate rabbitTemplate;

	@PostConstruct
	private void timerPush() {
		CompletableFuture.runAsync(() -> {
			while (true) {
				try {
					if (auditList.size() > 0) {
						String messageExt = "";
						messageExt = auditList.poll(10, TimeUnit.SECONDS);
						if (StringUtils.isNotBlank(messageExt)) {
							rabbitTemplate.convertAndSend(MqQueueConfig.LOG_AUDIT_EXCHANGE, MqQueueConfig.LOG_AUDIT_QUEUE, messageExt);
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, ThreadPoolUtils.getThreadPool());
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		String path = exchange.getRequest().getURI().getPath();
		if (isSkip(path)) {
			return chain.filter(exchange);
		}
		ServerHttpResponse resp = exchange.getResponse();
		String headerToken = exchange.getRequest().getHeaders().getFirst(AuthProvider.AUTH_KEY);
		String paramToken = exchange.getRequest().getQueryParams().getFirst(AuthProvider.AUTH_KEY);
		if (StringUtils.isBlank(headerToken) && StringUtils.isBlank(paramToken)) {
			return unAuth(resp, "缺失令牌,鉴权失败");
		}
		String auth = StringUtils.isBlank(headerToken) ? paramToken : headerToken;
		String token = JwtUtil.getToken(auth);
		Claims claims = JwtUtil.parseJWT(token);
		if (claims == null) {
			return unAuth(resp, "请求未授权");
		}

		// logAudit 查询类的不审计
		if (!path.contains("dictionary") && !path.contains("list") && !path.contains("select") && !path.contains("tree") && !path.contains("detail")) {
			ServerHttpRequest request = exchange.getRequest();
			String method = request.getMethodValue();
			Map<String, Object> mapClaims = JSONObject.parseObject(JSONObject.toJSONString(claims), Map.class);
			Long userId = new Long(String.valueOf(mapClaims.get("user_id")));

			if (HttpMethod.POST.matches(method)) {
				return DataBufferUtils.join(exchange.getRequest().getBody())
					.flatMap(dataBuffer -> {
						byte[] bytes = new byte[dataBuffer.readableByteCount()];
						dataBuffer.read(bytes);
						String bodyString = new String(bytes, StandardCharsets.UTF_8);
						logTrace(exchange, bodyString);
						pushAuditMq(path, userId, bodyString);

						exchange.getAttributes().put("POST_BODY", bodyString);
						DataBufferUtils.release(dataBuffer);
						Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
							DataBuffer buffer = exchange.getResponse().bufferFactory()
								.wrap(bytes);
							return Mono.just(buffer);
						});

						ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
							exchange.getRequest()) {
							@Override
							public Flux<DataBuffer> getBody() {
								return cachedFlux;
							}
						};
						return chain.filter(exchange.mutate().request(mutatedRequest)
							.build());
					});
			} else if (HttpMethod.GET.matches(method)) {
				Map m = request.getQueryParams();
				String bodyString = m.toString();
				logTrace(exchange, bodyString);
				pushAuditMq(path, userId, bodyString);
			}
		}

		return chain.filter(exchange);
	}


	/**
	 * 行为审计信息推送
	 * @param path
	 * @param userId
	 * @param param
	 */
	private void pushAuditMq(String path, Long userId, String param) {
		// 过滤敏感信息
		JSONObject jsonObject = JSONObject.parseObject(param);
		jsonObject.remove("password");
		jsonObject.remove("password2");
		param = jsonObject.toJSONString();

		Map<String, Object> auditMap = new HashMap<>(6);
		auditMap.put("path", path);
		auditMap.put("accessMode", "http");
		auditMap.put("userId", userId);
		auditMap.put("param", param);
		String toJSONFromAuditLog = JSONObject.toJSONString(auditMap);
		auditList.add(toJSONFromAuditLog);
		System.out.println("行为审计信息:" + toJSONFromAuditLog);
	}


	/**
	 * 日志信息
	 * @param exchange
	 * @param param    请求参数
	 */
	private void logTrace(ServerWebExchange exchange, String param) {
		ServerHttpRequest serverHttpRequest = exchange.getRequest();
		String path = serverHttpRequest.getURI().getPath();
		String method = serverHttpRequest.getMethodValue();
		String headers = serverHttpRequest.getHeaders().entrySet()
			.stream()
			.map(entry -> "            " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
			.collect(Collectors.joining("\n"));
		log.debug("\n" + "----------------             ----------------             ---------------->>\n" +
				"HttpMethod : {}\n" +
				"Uri        : {}\n" +
				"Param      : {}\n" +
				"Headers    : \n" +
				"{}\n" +
				"\"<<----------------             ----------------             ----------------"
			, method, path, param, headers);
	}

	private boolean isSkip(String path) {
		return AuthProvider.getDefaultSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
			|| authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
	}

	private Mono<Void> unAuth(ServerHttpResponse resp, String msg) {
		resp.setStatusCode(HttpStatus.UNAUTHORIZED);
		resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
		String result = "";
		try {
			result = objectMapper.writeValueAsString(ResponseProvider.unAuth(msg));
		} catch (JsonProcessingException e) {
			log.error(e.getMessage(), e);
		}
		DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
		return resp.writeWith(Flux.just(buffer));
	}

	@Override
	public int getOrder() {
		return -100;
	}

}
